/**
 * JavaFileSystem is an implementation of a VFS in Java. An operating system using the filesystem should be capable
 * of performing reads, writes, opens, etc on the simulated disk.
 * 
 * Current Issues:
 * 1. Indirection is not properly and fully implemented
 * 2. Allocation of disk space across multiple blocks is not working as desired. Currently, an exception is thrown.
 *
 *
 *@author Steven Turner
 *@author Adam Shenk
 *@author Mike Conatser
 */


/**
 * @author Administrator
 *
 */
class JavaFileSystem implements FileSystem {
	// Set up the constants for the whence field in seek
	public static final int SEEK_SET = 0;
	public static final int SEEK_CUR = 1;
	public static final int SEEK_END = 2;

	// variables used in VFS implementation
	//open files in the VFS
	private FileTable ft;
	//the virtual disk
	private Disk disk;
	//the superblock of the disk to track how many blocks, index blocks, and the start of the freelist.
	private SuperBlock sb;

	
	/**
	 * Create the Java VFS.
	 */
	public JavaFileSystem() {
		//Init Disk
		disk = new Disk();
		//Init Superblock and try to load superblock from disk if the file already exists.
		sb = new SuperBlock();
		try {
			disk.read(0, sb);
		} catch (Exception e) {
			error("No diks with superblock and the following exception occured: " + e);
		}
		//not necessary
		sb.freeList =1;
		//Initialize FileTable
		ft = new FileTable();

	}
	
	
	
	/** 
	 * Initializes the disk to a state representing an empty filesystem. Fills in the superblock and links all data blocks
	 * to the built up freelist composed of inodeblocks.
	 *
	 * @param size the number of blocks on the disk
	 * @param isize the number of index blocks;
	 */
	public int formatDisk(int size, int iSize) {
		if (iSize > size - 1) {
			error("Yer diks isize is too big");
			return (-1);
		}
		// the size of our partition on disk
		sb.size = size;
		sb.iSize = iSize;
		//Freelist starts immediately after the inodeblocks.
		sb.freeList = iSize + 1;
		// write the "partition" to disk

		disk.write(0, sb);
		// put some empty index blocks on the diks starting after the superblock
		InodeBlock ib = new InodeBlock();
		for (int i = 1; i <= iSize; i++)
			disk.write(i, ib);
		// keep track of free blocks in a linked list of indirectblocks
		IndirectBlock indb = new IndirectBlock();
		for (int i = iSize + 1; i < size; i++) {
			if (i < size - 1)
				// point ter teh next blx
				indb.pointer[0] = i + 1;
			else
				
				indb.pointer[0] = 0;
			disk.write(i, indb);
		}
		return (0);
	} // formatDisk

	
	/** 
	 * Close all the files currently open, write them out to the disk, and stop the virtual disk.
	 * 
	 */
	public int shutdown() {

		// close all files that are still valid in file table
		for (int i = 0; i < ft.fdArray.length; i++) {

			if (ft.isValid(i)) {
				close(i);
			}
		}
		// synchronize free list (involves a write)

		// synchronize superblock (involves a write)
		disk.write(0, sb);

		// stop the disk
		disk.stop();

		// return what?
		return 0;
	} // shutdown

	

	/**
	 *  Creates a new empty file and opens it in the filetable. Filepointers set to 0, initally one file.
	 *  @return the filedescriptor id of the file range 0-20
	 */
	public int create() {

		// problem: theres a finite number of file table entries

		InodeBlock current = new InodeBlock();
		//Keep track of inumber
		//look for a free inode by scanning over all blocks starting past superblock.
		int q = 1;
		for (int i = 1; i <= sb.iSize; i++) {
			disk.read(i, current);
			
			for (int j = 0; j < current.node.length; j++) 
			{
				Inode inode = current.node[j];

				// we found a free inode
				if (inode.flags == 0) {
					// allocate a new filetable spot
					int fileDescr = ft.allocate();
					if (fileDescr >= 0) {
						//mark as used.
						inode.flags = 1;
						inode.owner = q; //Set Inumber in the owner field.
						inode.fileSize = 0;
						//Set all file pointers to 0.
						for (int k = 0; k < inode.pointer.length; k++)
							inode.pointer[k] = 0;
						//Save the inode in the index block.
						disk.write(i, current);
						//mark the filetable as occupied for this filedescriptor
						ft.bitmap[fileDescr] = 1; 
						//store filedescriptor pointer to inode for caching
						ft.fdArray[fileDescr] = new FileDescriptor(inode, q);

					}
					return (fileDescr);
				}
				//increment iNumber (Note: this is super wrong and storing this value in the owner field is a hack)
				q++;
			}
		}
		// if you are unable to allocate an entry,
		// error
		error("No free inodeszes.");
		return (-1);

		//NOTES:
		// if you are able to create a file,
		// build a new inode
		// allocate that inode
		// write it to the disk

		// call open -- return the file descriptor that is returned from open

	} // create

	
	/**
	 * Locates an existing file on the disk. Allocate in the filetable for caching.
	 * @param iNumber the filenumber to be opened from the disk.
	 * @return the filedescriptor of the file.
	 */
	public int open(int iNumber) {
		//NOTES:
		// check input to make sure its valid - make sure that inumber is a
		// positive (possibly 0) number
		// and it doesnt fall outside of the bounds of the disk
		// int fd =-1;

		// Inode current = ft.getFDfromInumber(iNumber);
		// read inode which is a (function in JFS)
		// if the inode you get returns null,
		// error
		
		
		//Check and see if the file is actually already open!
		for (int i = 0; i < ft.bitmap.length; i++) 
		{
			if (ft.bitmap[i] != 0) 
				if (ft.fdArray[i].getInumber() == iNumber)
					//The file IS open already.
					return (-1); 
		}

		InodeBlock ib = new InodeBlock();
		//Find the proper inode on the disk by looping through iBlocks.
		for (int i = 1; i <= sb.iSize; i++) 
		{
			disk.read(i, ib);
			//Loop through the inodes to find the node with Inumber.
			for (int j = 0; j < ib.node.length; j++) {
				Inode inode = ib.node[j];
				//We found a used inode with the proper iNumber
				if (inode.flags != 0 && inode.owner == iNumber)
				{
					//Allocate space for the file in the filetable
					int filedesc = ft.allocate(); 
					if (filedesc >= 0) {
						FileDescriptor fd = new FileDescriptor(inode, iNumber);
						ft.bitmap[filedesc] = 1;
						ft.fdArray[filedesc] = fd;
						return (filedesc);
					}
					error("Too many files are currently open!");
					return (-1);
				}
			}
		}
		error("No such file found.");
		return (-1);
		// if it works fine,
		// call an open() off of inumber and inode (1 param open)
	} // open
	
	//NOTES:
	// Read up to buffer.length bytes from the open file indicated by fd,
	// starting at the current seek pointer, and update the seek pointer.
	// Return the number of bytes read, which may be less than buffer.length
	// if the seek pointer is near the current end of the file.
	// In particular, return 0 if the seek pointer is greater than or
	// equal to the size of the file.
	/**
	 *  Read up to buffer.length bytes from the open file indicated by fd,
	 starting at the current seek pointer, and update the seek pointer.
	 Return the number of bytes read, which may be less than buffer.length
	 if the seek pointer is near the current end of the file.
	 In particular, return 0 if the seek pointer is greater than or
	 equal to the size of the file.
	 * @return The error.
	 * @param fd the filedescriptor id
	 * @param buffer the buffer to read the bytes of the file into
	 */
	public int read(int fd, byte[] buffer) {
		// start at current seek pointer

		// i think i lost some stuff alan said here

		// return 0 if seek pointer is >= size of file

		// may return less than buffer length because you may not have that much
		// to return

		// check input to make sure file descriptor is valid in file table
		if (!ft.isValid(fd)) 
		{
			error("File needs to be opened!");
			return (-1); 
		}
		
		//Determine the location on the disk!
		int localseekpointer = ft.getSeekPointer(fd);
		int logicalblockNo = localseekpointer / Disk.BLOCK_SIZE; 
		int offset = localseekpointer % Disk.BLOCK_SIZE; 

		//Get the files inode from the filetable
		Inode inode = ft.getInode(fd); 

		byte[] blockStoreBuff = new byte[Disk.BLOCK_SIZE];
		//How far we've read into the buffer.
		int buffpt = 0; 
		
		//how many bytes we've read from the disk to ensure we don't go overbounds. (still wonky)
		int numbytesread = 0;
		//As long as our our bufferpointer has gone over the size of our read buffer
		//and filesize, populate the buffer with data read from the disk.
		while (buffpt < buffer.length && localseekpointer < inode.fileSize) {
			//Get the physical block from the disk based on logblock using Alan's Method. (Indirection?)
			int physicalBlockID = getBlock(inode, logicalblockNo);
			//If we get an invalid block id... error.
			if (physicalBlockID < 0)
				return (-1);
			//Read the data block into temporary buffer.
			disk.read(physicalBlockID, blockStoreBuff); 
			//Figure out whether we're reading up to the buffer length or blocksize with respect to the offset we start at
			//Tricky! :~)
			int bytesRead = Math.min(Disk.BLOCK_SIZE - offset, buffer.length);
			//Copy the data read into the buffer passed in
			for (int i = 0; i < bytesRead; i++)
				buffer[buffpt++] = blockStoreBuff[offset + i]; 

			numbytesread += bytesRead;
			//update our seek and offset pointer to where we are on the disk now
			localseekpointer += bytesRead; 
			offset += bytesRead;
			
			//Assuming contiguous allocation here... jump to next physical block for reading.
			if (offset >= Disk.BLOCK_SIZE) {
				offset = 0;
				logicalblockNo++;
			}
			//Update the filetable to keep track of where we are in the file.
			ft.setSeekPointer(fd, localseekpointer); 
		}
		return (numbytesread);

		//NOTES:
		// get value of seek pointer, which is based off the file table

		// make sure you ignore empty buffers (<= 0 dont do anything)

		// get inode at that file descriptor from file table

		// handle the read, handling the read is gonna be a while loop,
		// want to find out what disk number is for block you are trying to get
		// out,
		// handled by fxn called GetBlock from javafilesystem

		// handle error if getblock returns -1 (something wasnt on the disk)

		// determine number of bytes to be read, then do the actual read
		// (calling read function from the disk)

		// do something about the buffer (alan was purposefully vague)

		// increment position on the disk once out of the while loop

		// update seek pointer

		// return the buffer position (endpoint of buffer)
	} // read

	
	/**
	 * Transfer buffer.length bytes from the buffer to the file, starting
	 * at the current seek pointer, and add buffer.length to the seek pointer.
	 * @param fd the filedescriptor
	 * @param buffer what is being written to the file.
	 */
	public int write(int fd, byte[] buffer) {
		
		//NOTES:
		// return -1;
		// check input to see if valid
		// ignore any empty buffers

		// once you have this, you may need to deal with certain errors
		// (if disk block has returned a number that doesnt make sense (<= 0),
		// or holes (gaps in the disk itself))

		// once you deal with those errors, determine number of bytes to write
		// (in the buffer itself),
		// determine whether or not youre gonna finish writing outside the
		// current EOF
		// may need to determine if you need to read the block before you write
		// it

		// you might not have all of your info
		// (in the case of a file append, you may be writing a block but half
		// the block is already on the disk,
		// then get it and concatenate it and then write it)

		// prepare data to actually do the write (may include handling reads if
		// you need the rest of the block)
		// take a look at disk.java for how its formatted

		// increment position on disk
		// update seek pointer and size of file as its stored in inode
		// information

		// actual updating of file size you can look at inode.java

		// return the buffer position like we did in read

		//Checking filedesc for validity! File must be opened.
		if (!ft.isValid(fd))
		{
			error("File must be opened first!");
			return (-1);
		}
		// get the value of the seek pointer (same as read)
		// get the inode for whatever file descriptor youre outputting to

		int seekpoint = ft.getSeekPointer(fd);
		// figure out the starting point of the write (on the disk)

		// handle write itself

		// determine what disk number of the block is (that youre about to write
		// to),
		// allocate a new block if necessary
		int logicalblock = seekpoint / Disk.BLOCK_SIZE;
		int offset = seekpoint % Disk.BLOCK_SIZE; 

		Inode inode = ft.getInode(fd);

		byte[] temporarydata = new byte[Disk.BLOCK_SIZE];
		int bufferpointer = 0;

		int bytessaved = 0;
		while (bufferpointer < buffer.length) {
			int physicalblock = 0;
			
			//If we're spanning a new block we've gotta allocate a new data block!!
			if (logicalblock >= Math.ceil(inode.fileSize / Disk.BLOCK_SIZE)) {
				// this is superbad and superwrong;
				//Not using Alan's addBlock call...
				
				physicalblock = allocateBlock(inode, logicalblock);
			} else {
				physicalblock = getBlock(inode, logicalblock);
			}
			if (physicalblock < 0){
				//Error... related to below where we're copying data to temp.
				return (-1);
			}
			
			disk.read(physicalblock, temporarydata);
			//Which is smaller? do we need to jump to a new block?
			int numbyteswritten = Math.min(Disk.BLOCK_SIZE - offset, buffer.length);
			for (int i = 0; i < numbyteswritten; i++){
				//if((offset+i < Disk.BLOCK_SIZE) && (bufferpointer +1 <buffer.length))
				try{
					
					temporarydata[offset+i] = buffer[bufferpointer++];
					//This is a bug we were unable to fix... partially due to allocateBlock and handling of offset.
				}catch (Exception e){
					error("Filesize too big, must be less than 512.");
					return -1;
				}
				
			}
			//Write the temporary data to the disk.
			disk.write(physicalblock, temporarydata);

			bytessaved += numbyteswritten;
			seekpoint += numbyteswritten;
			offset += numbyteswritten;
			//Assuming contiguous allocation here... jump to next physical block for writing.
			if (offset >= Disk.BLOCK_SIZE) {
				offset = 0;
				logicalblock++;
			}
			//Save the offset for other operations.
			ft.setSeekPointer(fd, seekpoint); 
			inode.fileSize += numbyteswritten;
		}
		return (bytessaved);
	} // write

	/**
	 * Update the seek pointer by the offset, according to whence
	 * Return the new value of the seek pointer
	 * If the new seek pointer would be negative, leave it unchanged
	 * 
	 * @param fd		The file descriptor
	 * @param offset	Distance from start of the block
	 * @param whence	Should be one of three values: 0, 1, or 2,
	 * 					meaning seek set, seek current, and seek end, respectively
	 * 
	 * @return 			The updated seek pointer if success
	 * 					-1 on error	
	 */
	public int seek(int fd, int offset, int whence) {

		// make sure fd is valid
		if (!ft.isValid(fd)) {
			error("Seek Error: Invalid fd");
			return -1;
		}

		// get old seek pointer
		int newSeekPointer = ft.getSeekPointer(fd);

		// get file size (file table)
		int fileSize = ft.fdArray[fd].getInode().fileSize;

		// whence is gonna be once of three values (0 1 2) represents seek set,
		// seek current, seek end (these are constants already)
		switch (whence) {
		case SEEK_SET:
			// set seek pointer to just the offset
			newSeekPointer = offset;
			break;
		case SEEK_CUR:
			// add offset to old seek pointer
			newSeekPointer += offset;
			break;
		case SEEK_END:
			// get to the end of the file, and then offset
			newSeekPointer = newSeekPointer + (fileSize - newSeekPointer) + offset;
			break;
		default:
			error("Seek Error: Invalid whence (needs to be 0, 1, or 2, but was set to " + whence);
			return -1;
		}

		// if new seek pointer is negative, error
		if (newSeekPointer < 0) {
			error("Seek Error: New seek pointer was going to be negative");
			return -1;
		}

		// set the seek pointer and return it
		ft.setSeekPointer(fd, newSeekPointer);
		return newSeekPointer;
	} // seek

	/**
	 * Write the Inode back to the disk and free the corresponding file table entry
	 * 
	 * @param fd		The file descriptor 
	 * 
	 * @return 			0 on success
	 * 					-1 on error	
	 */
	public int close(int fd) {
		
		//make sure fd is valid
		if (!ft.isValid(fd)){ 
			error("close(int) Error: invalid file descriptor"); 
			return -1; 
		}
		
		// get file's inode
		Inode inode = ft.getInode(fd);
		InodeBlock ib = new InodeBlock();

		boolean updated = false;
		
		// loop over all inode blocks
		for (int i = 0; i <= sb.iSize; i++)
		{
			disk.read(i, ib);
			updated = false;
			
			// loop over inodes in the block
			for (int j = 0; j < ib.node.length; j++)
			{
				Inode inode2 = ib.node[j];

				// System.out.println("Inode2 flag" +inode2.flags);
				
				//file exists
				if ((inode2.flags != 0) && (inode2.owner == inode.owner))
				{
					System.out.println("Found inode");
					
					//update the inode
					ib.node[j] = inode;
					
					//save changes
					disk.write(i, ib);
					
					updated = true;
					break;
				}

			}
			if (updated)
				break;
		}
		if (!updated) {
			error("Inode not found");
			return (-1);
		}

		//free the file table entry
		ft.free(fd);
		return (0);
	} // close

	/**
	 * Delete the file with the given Inumber, freeing all of its blocks
	 * 
	 * @param iNumber	The number of the Inode to be deleted
	 * 
	 * @return 			0 if successful
	 * 					-1 on error	
	 */
	public int delete(int iNumber) {

		// make sure we arent attempting to delete an open file
		Inode currentInode = readINode(iNumber);
		if (fileEntryExists(iNumber)) {
			error("Delete Error: Attempt to delete open file");
			return -1;
		}

		// get inode for passed in inumber
		Inode inode = readINode(iNumber);

		// if inode is null, its not valid.
		if (inode == null) {
			error("Delete Error: invalid inode");
			return -1;
		}

		// if inode flag is 0, its a free block
		if (inode.flags == 0) {
			error("Delete Error: attempt to delete a free block");
		}

		// get size of the file
		int filesize = inode.fileSize;

		// get size of file in blocks (can span 1-2 blocks, requires minor math)
		// file will usually only be in 1 block (size is filesize (max 64b), block size is 512b)
		int fileSizeInBlocks = 1;

		// check to see if the file overlaps onto the next block
		// PROBLEM: Have not figured out this calculation
		boolean fileSpansTwoBlocks = false;

		if (fileSpansTwoBlocks) {
			fileSizeInBlocks = 2;
		}

		// deallocate all of the data blocks (requires call to a function freeBlock)
		// PROBLEM: Not yet actually deallocating anything
		while (fileSizeInBlocks > 0) {
		//	freeBlock(iNumber);
			fileSizeInBlocks--;
		}

		// need to deal with blocks on multiple levels (indirect blocks)
		// PROBLEM: Not dealing with indirection

		// flag inode as free
		inode.flags = 0;

		// write back to the disk
		writeInode(iNumber, inode);

		return 0;
	} // delete

	private void error(String err) {
		System.err.println(err);
	}

	public String toString() {
		String s = disk.generateStats();
		s.concat(sb.toString());
		// what about file table?
		return s;
	}

	// ================================================================================================================================
	// ======================================================= HELPER FUNCTIONS
	// =======================================================
	// ================================================================================================================================

	public int allocateInode(Inode inode) {
		// find and allocate the first free inode
		// traverse inode section of disk by going through from 1 - isize
		for (int i = 1; i <= sb.iSize; i++) {

		}
		// traverse inode section of disk by going through from 1 - isize
		// read each block off disk
		// for each block on disk, check its free flag
		// if we find it, return the inode of that particular block (call to
		// inodenumber())
		// if we get thru both for loops and we dont find a free file, disk is
		// full, cant allocate (send error and return -1)

		// return the index of the free inode (PARAMS NOT RIGHT)
		return inodeNumber(5, 5);
	}

	// return absolute inode number
	public int inodeNumber(int iblockNumber, int inodeOffset) {
		Inode inode = new Inode();
		return (iblockNumber - 1) * (disk.BLOCK_SIZE / inode.SIZE)
				+ inodeOffset + 1;
	}

	public int open(int inumber, Inode inode) {
		return -1;
		// first thing is to make sure inode is valid
		// if inode flag == 0, theres nothing in that inode and theres no file
		// there (error)

		// check to see if file is already open, check through file table
		// file table will be checked referencing this number inum, which is the
		// index of thefile table entry

		// if file table entry already exists, file is already open (error)

		// if both of those pass, we can work on opening file
		// first thing is to create file descriptor (int)
		// file descriptor can be generated to call to file table allocate()
		// function

		// make sure descriptor we get is within bounds (between 0 and max # in
		// file table)
		// (error)

		// assuming descriptor was fine, add information we have, the inode,
		// inum, and file descriptor, to the file table.
		// this can be done by making call to add() in file table

		// this returns a status value, check this to make sure that its not
		// less than 0.
		// if it is, that means the add() returned -1, which means file open
		// error (error)

		// if it was good, return the file descriptor
	}

	public Inode readINode(int iNumber) {

		// read inode returns an inode and takes in an integer (also called
		// inode)
		// this int is the inode number of the file that is going to be opened
		// first step is to check ot make sure inode value being passed in is >0
		// if its not, error and return NULL
		// once that is done, you can get corresponding inode block num by a
		// call to iblock (another helper function).

		// that integer you get back from iblock, check to make sure its >= 1
		// but less than isize
		// providing that passes, you can create a new inode block (call to
		// constructor for inode block)
		// do a disk read featuring the block number gotten by iblock and then
		// the inode block you just created

		// return the corresponding inode, so that is a specific index from
		// ib.node[ioffset(inode)]
		// this returns the actual inode that we will need

		// the node is in cache...so we can just read it out of cache...
		for (int iind = 0; iind < ft.fdArray.length; iind++) {
			if (ft.fdArray[iind].getInumber() == iNumber) {
				return (ft.fdArray[iind].getInode());

			} else {
				Inode i = readInodeFromDisk(iNumber);
				return i;
			}
		}
		// nothing found!
		error("No such inode.");
		return null;
	}

	private Inode readInodeFromDisk(int number) {
		InodeBlock ib = new InodeBlock();

		for (int i = 0; i <= sb.iSize; i++) // loop over all inode blocks
		{
			disk.read(i, ib);

			for (int j = 0; j < ib.node.length; j++) // loop over inodes in the
														// block
			{
				Inode inode2 = ib.node[j];

				// System.out.println("Inode2 flag" +inode2.flags);
				if (inode2.owner == number) // file exists
				{
					return ib.node[j];
				}

			}
		}
		error("No such inode on disk.");
		return null;

	}

	private boolean fileEntryExists(int iNumber) {

		for (int i = 0; i < ft.bitmap.length; i++) {
			int fdkey = ft.bitmap[i];
			if (fdkey == 1)
				return true;

		}
		return false;
	}

	// this writes an inode to the disk
	public int writeInode(int iNodeNumber, Inode tempINode) {

		// make sure inode # > 0
		if (iNodeNumber < 0) // or equal?
		{
			return -1;
		}

		// get a block number by call to iblock()
		int blockNum = iblock(iNodeNumber);

		// check block number thats generated by iblock() to make sure its in
		// bounds (same as read inode())
		if ((blockNum > disk.NUM_BLOCKS) || (blockNum < 0)) {
			error("WriteInode Error: Block out of bounds");
			return -1;
		}

		// if okay, create a new inodeblock (iblock) which requires constructor
		// call to inodeblock
		InodeBlock ib = new InodeBlock();

		// read off the disk the block denoted by block number you got from
		// iblock (3 lines before) storing it into this inode block ib
		disk.read(blockNum, ib);
		for (int i = 0; i < ib.node.length; i++) {
			if (ib.node[i].owner == iNodeNumber) {
				ib.node[i] = tempINode;
				disk.write(blockNum, ib);
				return 0;
			}
		}
		// next step is to update the inode (you have an inode that was just one
		// of your params,
		// determine what is the correct offset and update that particular entry
		// in the inode block that you just reado ff the disk,
		// and then write the inode block back to the disk

		// in order to determine the correct index, call to ioffset()

		// something fubared
		error("Unable to write inode to disk.");
		return -1;
	}

	/*
	 * Return the physical block number on a disk if we need a new block... source of errors.
	 * Pass in an inode, and logical block number and determine the physical block.
	 */
	private int allocateBlock(Inode inode, int theblock) {
		//validate there is space on the disk.
		if (sb.freeList <= 0) {
			error("The disk is full!");
			return (-1);
		}
		//find our first unused block.
		int freeblk = sb.freeList; 
		IndirectBlock ib = new IndirectBlock();
		//open the indirect block.
		disk.read(freeblk, ib); 
		//reassign freespace on the disk.
		sb.freeList = ib.pointer[0];

		//Haxing off of Alan's method...
		// get # from inode
		final int N = Disk.BLOCK_SIZE / 4;
		if (theblock < 10) 
		{
			inode.pointer[theblock] = freeblk;
		} else if (theblock < 10 + N) {
			IndirectBlock ib2 = new IndirectBlock();
			//First order indirection
			int physBlockNo = inode.pointer[10]; 
			disk.read(physBlockNo, ib2);
			ib2.pointer[theblock - 10] = freeblk;
		} else if (theblock < 10 + N + N * N)
		{
			IndirectBlock ib2 = new IndirectBlock();
			//Second order indirection
			int physBlockNo = inode.pointer[11];
			disk.read(physBlockNo, ib2);
			physBlockNo = ib2.pointer[(theblock - 10 - N) / Disk.BLOCK_SIZE];
			disk.read(physBlockNo, ib2);
			ib2.pointer[(theblock - 10 - N) % Disk.BLOCK_SIZE] = freeblk;
		} else {
			//We're not doing third order...
			error("Too large block number");
			return (-1);
		}
		//Return the free physical block number.
		return (freeblk);
	}

	// determines whatever the block number is of a given inode
	public int iblock(int inodenum) {
		double blockSize = (double) disk.BLOCK_SIZE;
		double floatVersion = (inodenum * 32) / blockSize;
		int intVersion = (int) floatVersion;

		if (floatVersion - intVersion > 0) {
			intVersion++;
		}
		if (intVersion == 0) {
			intVersion = 1;
		}
		return intVersion;
	}

	public int ioffset(int inodenum) {
		// see above
		return -1;
	}

	// Return the inumber of an open file
	public int inumber(int fd) {

		// make sure fd is valid
		if (!ft.isValid(fd)) {
			return -1;
		}

		// theres a function for getting inumber for a valid open file
		return ft.getInumber(fd);
	}

	// first thing - check free space

	// if for some reason the free list (cached first block) is null,
	// then we need to re-cache the first block (freelist = new indirect
	// block(),
	// read the freelist off of the disk and put it into the cached first block)

	// find the first non-empty block on the free list
	// determine the offset of the first free block

	// special case you need to deal with: if free list block is empty (simple)

	// if not empty, return this free block you just picked up

	public int allocateBlock(int fd, int where) {

		// take fd and make sure its describing a file that is open

		// check to make sure we have space on disk (look at superblocks free
		// list and make sure that there is space in freelist still)
		if (sb.freeList <= 0) {
			error("allocateBlock(int, int): No free space on disk");
			return -1;
		}

		// allocate a block
		int blockNum = allocateBlock();

		// if block returned from allocateBlock == -1
		if (blockNum == -1) {
			error("allocateBlock(int, int) Error: allocateBlock() returned -1");
			return -1;
		}

		// get the inode from the file descriptor
		Inode inode = ft.getInode(fd);

		// add the block
		int returnedFromAddblock = addBlock(inode, blockNum, where);

		// return the block number
		return blockNum;
	}

	/*
	 * 
	 * 
	 * 
	 * public int getBlock(int block){ //takes a block number and returns an
	 * integer //frees the given block //want to check input to make sure its a
	 * valid block //need to deal with case if there are no free blocks left
	 * //(this will be the first block added to an empty free list)
	 * 
	 * //if the freelist has stuff in it already, //go through free list itself
	 * and figure out what the last unused element is in the indirect blocks
	 * pointer data structure
	 * 
	 * //add the block to the list //the way you do this is
	 * freelist.pointer[offset] = block //there are special cases you need to
	 * deal with }
	 * 
	 * //
	 * ========================================================================
	 * ============================================================== //
	 * ======================================================== ALANS FINISHED
	 * FUNCTIONS ==================================================== //
	 * ========
	 * ==================================================================
	 * ============================================================
	 */
	/*
	 * Read the block number
	 * 
	 * @param inode the inode
	 * 
	 * @param block the logical block number
	 * 
	 * @return the corresponding block number on the disk, 0 if not available,
	 * -1 on error
	 */
	private int getBlock(Inode inode, int block) {

		// Figure out the size of file in blocks and validate the block number

		int size = (inode.fileSize + Disk.BLOCK_SIZE - 1) / Disk.BLOCK_SIZE;

		if (block < 0)
			return -1;
		if (block >= size)
			return 0;

		// Determine the level of indirection and relative indices

		// N = pointers per indirect block
		// level = the level of indirection
		// p = index of the block relative to first block of same indirection
		// level
		// i0 = index in the inode
		// i1, i2, i3 = indices in the first, second, and third indirect blocks

		// Please refer to the design document for details

		final int N = Disk.BLOCK_SIZE / 4;

		int level = 0;
		int p = 0;
		int i0 = 0;
		int i1 = 0;
		int i2 = 0;
		int i3 = 0;

		if (block <= 9) { // direct blocks
			level = 0;
			i0 = p = block;
		} else if (block <= (9 + N)) { // single indirect
			level = 1;
			p = block - 10;
			i0 = 10;
			i1 = p;
		} else if (block <= (9 + N + N * N)) { // double indirect
			level = 2;
			p = block - (10 + N);
			i0 = 11;
			i1 = p / N;
			i2 = p % N;
		} else if (block <= (9 + N + N * N + N * N * N)) { // triple indirect
			level = 3;
			p = block - (10 + N + N * N);
			i0 = 12;
			i1 = p / (N * N);
			i2 = (p / N) % N;
			i3 = p % N;
		} else {
			error("The file is too big");
			return -1;
		}

		// Level 0

		if (level == 0)
			return inode.pointer[i0];

		// Levels 1, 2, and 3

		// ib = indirect block

		IndirectBlock ib = new IndirectBlock();

		// Read first indirect block to ib

		// disk_i1 = block number of the first indirect block

		int disk_i1 = inode.pointer[i0];
		if (disk_i1 <= 0)
			return -1;
		else
			disk.read(disk_i1, ib);

		// Read first indirect block (Level 1 only)

		if (level == 1)
			return ib.pointer[i1];

		// Read second indirect block to ib

		// disk_i2 = block number of the second indirect block

		int disk_i2 = ib.pointer[i1];
		if (disk_i2 <= 0)
			return -1;
		else
			disk.read(disk_i2, ib);

		// Read second indirect block (Level 2 only)

		if (level == 2)
			return ib.pointer[i2];

		// Read third indirect block to ib - Level 3 only

		// disk_i3 = block number of the second indirect block

		int disk_i3 = ib.pointer[i2];
		if (disk_i3 <= 0)
			return -1;
		else
			disk.read(disk_i3, ib);

		// Read third indirect block

		return ib.pointer[i3];
	}

	/**
	 * Add a block to the inode
	 * 
	 * @param inode
	 *            the inode
	 * @param block
	 *            the block number
	 * @param where
	 *            logical block number in the file
	 * @return 0 if successful, or -1 if error
	 **/
	private int addBlock(Inode inode, int block, int where) {

		// Determine the level of indirection and relative indeces

		// N = pointers per indirect block
		// level = the level of indirection
		// p = index of the block relative to first block of same indirection
		// level
		// i0 = index in the inode
		// i1, i2, i3 = indeces in the first, second, and third indirect blocks

		// Please refer to the design document for details

		final int N = Disk.BLOCK_SIZE / 4;

		int level = 0;
		int p = 0;
		int i0 = 0;
		int i1 = 0;
		int i2 = 0;
		int i3 = 0;

		if (where <= 9) {
			level = 0;
			i0 = p = where;
		} else if (where <= (9 + N)) {
			level = 1;
			p = where - 10;
			i0 = 10;
			i1 = p;
		} else if (where <= (9 + N + N * N)) {
			level = 2;
			p = where - (10 + N);
			i0 = 11;
			i1 = p / N;
			i2 = p % N;
		} else if (where <= (9 + N + N * N + N * N * N)) {
			level = 3;
			p = where - (10 + N + N * N);
			i0 = 12;
			i1 = p / (N * N);
			i2 = (p / N) % N;
			i3 = p % N;
		} else {
			error("The file is too big");
			return -1;
		}

		// debug("Adding block " + block + " to level " + level + ": p = " + p +
		// ", i0 = " + i0 + ", i1 = " + i1 + ", i2 = " + i2 + ", i3 = " + i3);

		// Level 0

		if (level == 0) {
			inode.pointer[i0] = block;
			return 0;
		}

		// Levels 1, 2, and 3

		// allocated = number of allocated blocks
		// allocatedBocks = nmbers of allocated blocks
		// ib = indirect block

		int allocated = 0;
		int[] allocatedBlocks = new int[3];
		IndirectBlock ib = new IndirectBlock();

		// Read first indirect block to ib

		// disk_i1 = block number of the first indirect block

		int disk_i1 = inode.pointer[i0];
		if (disk_i1 <= 0) {

			// Allocate corresponding number of blocks

			for (int i = 0; i < level; i++) {

				int b = allocateBlock();

				if (b <= 0) {

					// Error occured, do cleanup and exit

					for (int j = 0; j < i; j++)
						freeBlock(allocatedBlocks[j]);
					return -1;
				}

				allocatedBlocks[i] = b;
				allocated++;
			}

			// Add the first one to the inode

			disk_i1 = inode.pointer[i0] = allocatedBlocks[--allocated];

			ib.clear();
		} else {
			// debug("addBlock: reading i1 block " + disk_i1);
			disk.read(disk_i1, ib);
		}

		// Update and write first indirect block (Level 1 only)

		if (level == 1) {
			ib.pointer[i1] = block;
			disk.write(disk_i1, ib);
			return 0;
		}

		// Read second indirect block

		// disk_i2 = block number of the second indirect block

		boolean toBeAllocated = allocated > 0;
		int disk_i2 = (toBeAllocated) ? (allocatedBlocks[--allocated])
				: (ib.pointer[i1]);
		if (toBeAllocated || (disk_i2 <= 0)) {
			if (disk_i2 <= 0) {

				if (allocated > 0) {
					error("Internal error"); // Should never happen
					for (int j = 0; j < allocated; j++)
						freeBlock(allocatedBlocks[j]);
					return -1;
				}

				// Allocate the corresponding number of blocks

				for (int i = 0; i < level - 1; i++) {

					int b = allocateBlock();

					if (b <= 0) {

						// Error occured, do cleanup and exit

						for (int j = 0; j < i; j++)
							freeBlock(allocatedBlocks[j]);
						return -1;
					}

					allocatedBlocks[i] = b;
					allocated++;
				}

				// Add the first one to the first indirect block

				disk_i2 = ib.pointer[i1] = allocatedBlocks[--allocated];
				disk.write(disk_i1, ib);

				ib.clear();
			} else {

				// It was already allocated

				ib.pointer[i1] = disk_i2;
				disk.write(disk_i1, ib);

				ib.clear();
			}
		} else {
			// debug("addBlock: reading i2 block " + disk_i2);
			disk.read(disk_i2, ib);
		}

		// Update and write second indirect block (Level 2 only)

		if (level == 2) {
			ib.pointer[i2] = block;
			disk.write(disk_i2, ib);
			return 0;
		}

		// Read third indirect block - Level 3 only

		// disk_i3 = block number of the second indirect block

		toBeAllocated = allocated > 0;
		int disk_i3 = (toBeAllocated) ? (allocatedBlocks[--allocated])
				: (ib.pointer[i2]);
		if (toBeAllocated || (disk_i3 <= 0)) {
			if (disk_i3 <= 0) {

				if (allocated > 0) {
					error("Internal error"); // Should never happen
					for (int j = 0; j < allocated; j++)
						freeBlock(allocatedBlocks[j]);
					return -1;
				}

				// Allocate an indirect block

				int b = allocateBlock();

				if (b <= 0) {
					// Error occured, do cleanup and exit
					freeBlock(b);
					return -1;
				}

				// Add it to the second indirect block

				disk_i3 = ib.pointer[i2] = b;
				disk.write(disk_i2, ib);

				ib.clear();
			} else {

				// It was already allocated

				ib.pointer[i2] = disk_i3;
				disk.write(disk_i2, ib);

				ib.clear();
			}
		} else {
			// debug("addBlock: reading i3 block " + disk_i3);
			disk.read(disk_i3, ib);
		}

		// Update and write third indirect block

		ib.pointer[i3] = block;
		disk.write(disk_i3, ib);

		return 0;
	}

	/**
	 * Free an indirect block
	 * 
	 * @param block
	 *            the block number to free
	 * @param level
	 *            the level of indirection (0 == not an indirect block)
	 * @return number of data blocks freed if success, or -1 on error
	 */
	private int freeIndirect(int block, int level) {

		// Check input

		if (block == 0)
			return 0;
		if (block < 0)
			return -1;
		if (level < 0)
			return -1;

		// Level 0 (just free the block)

		if (level == 0) {
			return (freeBlock(block) < 0) ? (-1) : (1);
		}

		// Traverse through the list of blocks and free all of them

		// count = the number of blocks freed
		// ib = the indirect block

		int count = 0;
		IndirectBlock ib = new IndirectBlock();

		// Read the block

		disk.read(block, ib);

		// Traverse it

		for (int i = 0; i < Disk.BLOCK_SIZE / 4; i++) {

			if (ib.pointer[i] <= 0)
				continue;

			// Free the block

			int r = freeIndirect(ib.pointer[i], level - 1);

			// Update count

			if (r > 0) {
				count += r;
			}
		}

		freeBlock(block);

		return count;
	}

	private int allocateBlock() {
		if (sb.freeList <= 0) {

			error("allocateblock(): no free space on disk");
			return -1;
		}
		// first thing - check free space
		// if for some reason the free list (cached first block) is null,
		// we need to re-cache the first block
		// (freelist = new indirect block(), read the freelist off of the disk
		// and put it into the cached first block)
		IndirectBlock indb = new IndirectBlock();
		for (int i = sb.iSize + 1; i < sb.size; i++) {
			disk.read(i, indb);

		}

		return -1;
		// find the first non-empty block on the free list
		// determine the offset of the first free block

		// special case you need to deal with: if free list block is empty
		// (simple)

		// if not empty, return this free block you just picked up
	}

	// frees the given block
	private int freeBlock(int blockNum) {

		// want to check input to make sure its a valid block
		// need to deal with case if there are no free blocks left
		// (this will be the first block added to an empty free list)

		// if the freelist has stuff in it already,
		// go through free list itself and figure out what the last unused
		// element is in the indirect blocks pointer data structure

		// add the block to the list
		// the way you do this is freelist.pointer[offset] = block
		// there are special cases you need to deal with

		System.out.println("Entering unuzedBlock.");
		byte[] tmp = new byte[disk.BLOCK_SIZE];

		if (sb.freeList <= 0) {
			for (int u = 0; u < disk.BLOCK_SIZE; u++) {
				tmp[u] = 0;
			}
			disk.write(blockNum, tmp);
			sb.freeList = blockNum;
			return 0;
		}

		disk.read(sb.freeList, tmp);

		short[] pointers = new short[disk.BLOCK_SIZE / 2 - 1];
		byte[] b = new byte[2];

		for (int p = 2; p < pointers.length + 2; p++) {
			b[0] = tmp[p];
			b[1] = tmp[p + 1];

			pointers[p - 2] = ((short) (((b[0] & 0xff) << 8) + (b[1] & 0xff)));
		}

		for (int q = 0; q < pointers.length; q++) {
			if (pointers[q] == 0) {
				tmp[q * 2] = (byte) (blockNum >> 8);
				tmp[q * 2 + 1] = (byte) (blockNum);

				// write the updated free block back to diks
				disk.write(sb.freeList, tmp);
				return 0;
			}
		}

		// the block we got becomes the head of the list...

		// we didn't change anything to the block we read in,
		// so don't need to write it out

		for (int i = 0; i < disk.BLOCK_SIZE; i++) {
			tmp[i] = 0;
		}

		tmp[0] = (byte) (sb.freeList >> 8);
		tmp[1] = (byte) sb.freeList;

		sb.freeList = blockNum;// the new head is the free block we just got
		disk.write(sb.freeList, tmp);
		return -1;

	}

}
